// TOP SECRET
package com.yunji.framework_template.common.http;

import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.imageio.ImageIO;
import javax.net.ssl.SSLContext;

import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import com.yunji.framework_template.common.util.StreamUtil;
import com.yunji.framework_template.common.util.StringUtil;
import com.yunji.framework_template.common.web.HttpResult;

public class HttpConnection {

    private static final Logger                             logger      = Logger.getLogger(HttpConnection.class);

    private static final PoolingHttpClientConnectionManager ccm;

    private static final CloseableHttpClient                httpClient;

    private static final int                                BUFFER_SIZE = 10 * 1024;

    private static final Map<String, String>                header      = new HashMap<String, String>();

    static {
        DisableSSLCertificateCheckUtil.disableChecks();
        EasySSLProtocolSocketFactory easySSL = null;
        easySSL = new EasySSLProtocolSocketFactory();
        Protocol easyhttps = new Protocol("https", (ProtocolSocketFactory) easySSL, 443);
        Protocol.registerProtocol("https", easyhttps);

        // header.put("Accept", "application/json, text/javascript, */*; q=0.01");
        // header.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        // header.put("Origin", "http://www.bestindiansites.com");
        // header.put("Refere", "http://www.bestindiansites.com/category/business/");
        header.put("User-Agent",
                   "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/70.0.3538.77 Chrome/70.0.3538.77 Safari/537.36");
        header.put("X-Requested-With", "XMLHttpRequest");
        header.put("Connection", "close");

        //System.setProperty("jsse.enableSNIExtension", "false");
        System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2,SSLv3");
        // System.setProperty("https.protocols", "TLSv1");
        // System.setProperty("https.protocols","TLSv1,TLSv1.2,TLSv1.1,TLSv1.0,SSLv3,SSLv2Hello");

        final Properties p = loadHttpConfProperties();
        final int maxTotal = Integer.parseInt(p.getProperty("http.max.total.connections"));
        final int maxPerRoute = Integer.parseInt(p.getProperty("http.default.max.connections.per.host"));
        // 指从连接池获取连接的timeout
        final int connectionRequestTimeout = Integer.parseInt(p.getProperty("http.connection.request.timeout"));
        // 指客户端和服务器建立连接的timeout,超时后会ConnectionTimeOutException
        final int connectionTimeout = Integer.parseInt(p.getProperty("http.connection.timeout"));
        // 指客户端从服务器读取数据的timeout,超出后会抛出SocketTimeOutException
        final int socketTimeout = Integer.parseInt(p.getProperty("http.so.timeout"));
        final int validateAfterInactivity = Integer.parseInt(p.getProperty("http.validate.after,inactivity"));
        final Boolean enableProxy = Boolean.parseBoolean(p.getProperty("http.proxy.enable"));

        HttpHost proxy = null;
        RequestConfig.Builder requestBuilder = RequestConfig.custom();

        if (enableProxy) {
            // 设置代理IP、端口、协议
            proxy = new HttpHost(p.getProperty("http.proxy.ip"), Integer.parseInt(p.getProperty("http.proxy.port")),
                                 p.getProperty("http.proxy.schema"));
        }
        if (proxy != null) {
            // 把代理设置到请求配置
            requestBuilder.setProxy(proxy);
        }
        RequestConfig requestConfig = requestBuilder.setConnectTimeout(connectionTimeout).setSocketTimeout(socketTimeout).setConnectionRequestTimeout(connectionRequestTimeout).build();

        /*
         * SSLConnectionSocketFactory sslcsf = createSSLConnectionSocketFactory(); Registry<ConnectionSocketFactory>
         * socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create().register("http",
         * PlainConnectionSocketFactory.INSTANCE) .register("https", sslcsf).build(); ccm = new
         * PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null,
         * Integer.parseInt(p.getProperty("http.connection.timeout")), TimeUnit.MILLISECONDS);
         */
        ccm = new PoolingHttpClientConnectionManager(Integer.parseInt(p.getProperty("http.connection.timeout")),
                                                     TimeUnit.MILLISECONDS);
        ccm.setDefaultMaxPerRoute(maxPerRoute);
        ccm.setMaxTotal(maxTotal);
        ccm.setValidateAfterInactivity(validateAfterInactivity);

        // 实例化CloseableHttpClient对象
        // httpClient =
        // HttpClients.custom().setSSLSocketFactory(sslcsf).setConnectionManager(ccm).setDefaultRequestConfig(requestConfig).setMaxConnPerRoute(maxPerRoute).setMaxConnTotal(maxTotal).build();
        httpClient = HttpClients.custom().setConnectionManager(ccm).setDefaultRequestConfig(requestConfig).setMaxConnPerRoute(maxPerRoute).setMaxConnTotal(maxTotal).build();

    }

    /**
     * 创建一个SSL信任所有证书的httpClient对象
     * 
     * @return
     */
    public static SSLConnectionSocketFactory createSSLConnectionSocketFactory() {
        try {
            SSLContext sslContext = new SSLContextBuilder().setProtocol("TLS").loadTrustMaterial(null,
                                                                                                 new TrustStrategy() {

                                                                                                     // 默认信任所有证书
                                                                                                     public boolean isTrusted(X509Certificate[] arg0,
                                                                                                                              String arg1) throws CertificateException {
                                                                                                         return true;
                                                                                                     }
                                                                                                 }).build();
            // AllowAllHostnameVerifier: 这种方式不对主机名进行验证，验证功能被关闭，是个空操作(域名验证)
            // SSLConnectionSocketFactory sslcsf = new SSLConnectionSocketFactory(sslContext,
            // SSLConnectionSocketFactory.getDefaultHostnameVerifier());
            SSLConnectionSocketFactory sslcsf = new SSLConnectionSocketFactory(sslContext,
                                                                               NoopHostnameVerifier.INSTANCE);
            return sslcsf;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 初使化连接参数
     * 
     * @return
     */
    private static Properties loadHttpConfProperties() {
        Properties p = new Properties();
        try {
            p.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("httpConfig.properties"));
        } catch (IOException e) {
            logger.error("找不到 HTTP配置文件：httpConfig.properties", e);
        }
        return p;
    }

    /**
     * 发起Post请求，并返回SdkHttpResult对象
     * 
     * @param url 请求的URL地址
     * @param content 发送的内容
     * @return
     * @throws HttpException
     * @throws IOException
     */
    public static HttpResult doPost(String url, String content) throws HttpException, IOException {
        return doPost(url, content, null);

    }

    /**
     * 发起Post请求，并返回SdkHttpResult对象
     * 
     * @param url 请求的URL地址
     * @param content 发送的内容
     * @param header 请求头
     * @return
     * @throws ParseException
     * @throws IOException
     */
    public static HttpResult doPost(String url, String content, Map<String, String> header) throws ParseException,
                                                                                            IOException {
        // 创建一个HttpMethod的实例
        HttpPost post = new HttpPost(url);
        try {
            addHeader(post, header);
            post.setEntity(new StringEntity(content, Consts.UTF_8));
            HttpResponse response;
            response = httpClient.execute(post);

            String result = null;
            if (response.getStatusLine().getStatusCode() == 200) {
                result = EntityUtils.toString(response.getEntity());
            } else {
                post.abort();
            }
            return new HttpResult(response.getStatusLine().getStatusCode(), result, response.getAllHeaders());
        } finally {
            EntityUtils.consume(post.getEntity());
            if (post != null) {
                post.releaseConnection();
            }
        }
    }

    /**
     * 发起Post请求，并返回SdkHttpResult对象
     * 
     * @param url 请求的URL地址
     * @param param 发送的内容
     * @param header 请求头
     * @return
     * @throws ParseException
     * @throws IOException
     */
    public static HttpResult doPost(String url, Map<String, String> param) throws ParseException, IOException {
        return doPost(url, param, null);

    }
    
    public static HttpResult doPost(String url, Map<String, String> param,
                                    Map<String, String> header) throws ParseException, IOException {
        return doPost(url, param, header, null);
    }

    /**
     * 发起Post请求，并返回SdkHttpResult对象
     * 
     * @param url 请求的URL地址
     * @param param 发送的内容
     * @param header 请求头
     * @return
     * @throws ParseException
     * @throws IOException
     */
    public static HttpResult doPost(String url, Map<String, String> param,
                                    Map<String, String> header,CookieStore cookieStore) throws ParseException, IOException {
        // 创建一个HttpMethod的实例
        HttpPost post = new HttpPost(url);
        try {
            addHeader(post, header);
            // 设置参数
            Set<Entry<String, String>> set = param.entrySet();
            List<NameValuePair> data = new ArrayList<NameValuePair>();
            for (Entry<String, String> entry : set) {
                data.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            post.setEntity(new UrlEncodedFormEntity(data, Consts.UTF_8));
            HttpClientContext context = HttpClientContext.create();
            if (cookieStore != null) {
                context.setCookieStore(cookieStore);
            }
            HttpResponse response = httpClient.execute(post,context);

            String result = null;
            if (response.getStatusLine().getStatusCode() == 200) {
                result = EntityUtils.toString(response.getEntity());
            } else {
                post.abort();
            }
            return new HttpResult(response.getStatusLine().getStatusCode(), result, response.getAllHeaders(),context.getCookieStore());
        } finally {
            EntityUtils.consume(post.getEntity());
            if (post != null) {
                post.releaseConnection();
            }
        }
    }

    /**
     * 执行get请求
     * 
     * @param urlSystem.setProperty("https.protocols", "TLSv1");
     * @return
     * @throws ClientProtocolException
     * @throws IOException
     */
    public static HttpResult doGet(String url) throws ClientProtocolException, IOException {
        return doGet(url, null);
    }

    public static HttpResult doGet(String url, Map<String, String> param) throws ClientProtocolException, IOException {
        return doGet(url, null, null);
    }

    public static HttpResult doGet(String url, Map<String, String> param,
                                   Map<String, String> header) throws ClientProtocolException, IOException {
        return doGet(url, param, header, null);
    }

    /**
     * 执行get请求
     * 
     * @param url
     * @param param 参数
     * @return
     * @throws ClientProtocolException
     * @throws IOException
     */
    public static HttpResult doGet(String url, Map<String, String> param, Map<String, String> header,
                                   CookieStore cookieStore) throws ClientProtocolException, IOException {
        HttpGet get = null;
        try {
            if (param != null && param.size() > 0) {
                // 设置参数
                Set<Entry<String, String>> set = param.entrySet();
                List<NameValuePair> data = new ArrayList<NameValuePair>();
                for (Entry<String, String> entry : set) {
                    data.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
                if (data.size() > 0) {
                    url = url + "?" + URLEncodedUtils.format(data, Consts.UTF_8);
                }
            }
            get = new HttpGet(url);
            addHeader(get, header);
            HttpClientContext context = HttpClientContext.create();
            if (cookieStore != null) {
                context.setCookieStore(cookieStore);
            }
            HttpResponse response = httpClient.execute(get, context);

            String result = null;
            if (response.getStatusLine().getStatusCode() == 200) {
                result = EntityUtils.toString(response.getEntity(),"utf-8");
            }
            return new HttpResult(response.getStatusLine().getStatusCode(), result, url, response.getAllHeaders(),
                                  context.getCookieStore());
        } finally {
            if (get != null) {
                get.abort();
                get.releaseConnection();
            }
        }
    }

    /**
     * 检查当前URL是否可以成功访问
     * 
     * @param url
     * @param param
     * @return
     * @throws ClientProtocolException
     * @throws IOException
     */
    public static HttpResult checkUrl(String url, Map<String, String> param) throws ClientProtocolException,
                                                                             IOException {
        HttpGet get = null;
        try {
            if (param != null && param.size() > 0) {
                // 设置参数
                Set<Entry<String, String>> set = param.entrySet();
                List<NameValuePair> data = new ArrayList<NameValuePair>();
                for (Entry<String, String> entry : set) {
                    data.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
                if (data.size() > 0) {
                    url = url + "?" + URLEncodedUtils.format(data, Consts.UTF_8);
                }
            }
            get = new HttpGet(url);
            HttpResponse response = httpClient.execute(get);
            return new HttpResult(response.getStatusLine().getStatusCode(), response.getAllHeaders());
        } finally {
            if (get != null) {
                get.abort();
                get.releaseConnection();
            }
        }
    }

    public boolean getRource(String source) {
        try {
            URL url = new URL(source);
            URLConnection uc = url.openConnection();
            InputStream in = uc.getInputStream();
            if (source.equalsIgnoreCase(uc.getURL().toString())) in.close();
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 给请求增加header
     * 
     * @param method
     * @param header
     */
    private static void addHeader(HttpRequestBase method, Map<String, String> header) {
        if(header==null || header.size()==0) {
            header = HttpConnection.header;
        }
        method.setHeader(new BasicHeader("Connection", "close"));
        if (header != null && header.size() > 0) {
            for (Map.Entry<String, String> entry : header.entrySet()) {
                method.setHeader(new BasicHeader(entry.getKey(), entry.getValue()));
            }
        }
        if ((method instanceof HttpPost) && (header == null || (StringUtils.isEmpty(header.get("Content-Type")) && StringUtils.isEmpty(header.get("content-type"))))) {// 用户没有设置Content-Type，则使用默认的Content-Type
            method.setHeader(new BasicHeader("Content-Type", "application/x-www-form-urlencoded"));
        }
    }

    public static void download(String url, String savePath) throws ClientProtocolException, IOException {
        FileOutputStream fos = null;
        BufferedInputStream bis = null;
        HttpGet get = null;
        try {
            get = new HttpGet(url);
            HttpResponse response = httpClient.execute(get);
            if (response.getStatusLine().getStatusCode() == 200) {
                byte[] buf = new byte[BUFFER_SIZE];
                bis = new BufferedInputStream(response.getEntity().getContent());
                fos = new FileOutputStream(savePath);
                int size = 0;
                while ((size = bis.read(buf)) != -1) {
                    fos.write(buf, 0, size);
                }
            }
        } finally {
            if (null != fos) {
                fos.close();
            }
            if (null != bis) {
                bis.close();
            }

            if (get != null) {
                get.abort();
                get.releaseConnection();
            }
        }
    }

    /**
     * 获取网络读取资源（图片、文件等）的流
     * 
     * @param url
     * @return
     * @throws Exception
     */
    public static BufferedImage readRemoteImage(String url) {
        HttpGet get = null;
        try {
            if (StringUtil.isEmpty(url)) {
                return null;
            }
            get = new HttpGet(url);
            HttpResponse response = httpClient.execute(get);
            if (response.getStatusLine().getStatusCode() == 200) {
                return ImageIO.read(response.getEntity().getContent());
            }
        } catch (Exception e) {
            logger.error("获取图片资源出错,url:" + url + ",err message：" + e.getMessage(), e);
        } finally {
            if (get != null) {
                get.releaseConnection();
                get.abort();
            }
        }
        return null;
    }

    /**
     * 从网络读取资源（图片、文件等）并加载到字节数组中
     * 
     * @param url
     * @param savePath
     * @return
     * @throws Exception
     * @throws UnsupportedOperationException
     */
    public static byte[] readRemoteFileToBytes(String url) {
        HttpGet get = null;
        try {
            get = new HttpGet(url);
            HttpResponse response = httpClient.execute(get);
            if (response.getStatusLine().getStatusCode() == 200) {
                return StreamUtil.readInputStream(response.getEntity().getContent());
            }
        } catch (Exception e) {
            logger.error("Get remote file to bytes exception happened:" + e.getMessage(), e);
        } finally {
            if (get != null) {
                get.releaseConnection();
                get.abort();
            }
        }
        return null;
    }

    public static Map<String, String> getDefaultHeader() {
        return header;
    }

    public static CloseableHttpClient getHttpclient() {
        return httpClient;
    }

}
